#include "gtest/gtest.h"
#include <sstream>

#include "helper/util/Subject.h"
#include "helper/util/Observer.h"
#include "helper/util/NotificationCategory.h"
#include "helper/unittest/Tracer.h"

namespace test {
namespace observer {

class DisconnectEvent : public helper::util::Event{
public:
    DisconnectEvent(int fd): fd(fd){}
    ~DisconnectEvent() override {}
    std::string toString() const {
        std::ostringstream oss;
        oss << "DisconnectEvent:fd:[" << fd << "]";
        return oss.str();
    }
private:
    int fd;
};
class DisconnectObserver : public helper::util::IPushObserver {
public:
    int update(helper::util::Event * event) override{
        TRACER.operation("DisconnectObserver-update-with-event");
        //DisconnectEvent * de = static_cast<DisconnectEvent*>(event);
        //std::cout << ((de == nullptr) ? std::string("null") : de->toString()) << std::endl;
        return 0;
    }
};

class Client  :  public helper::util::IPushSubject
    , public helper::util::ExactlyNotification<helper::util::IPushObserver> {
public:
    ~Client() override{}
    void disconnect(){
        TRACER.operation("disconnect-fire");
        DisconnectEvent ev(1);
        notify(&ev);
    }
    int notify(helper::util::Event * event) override{
        TRACER.operation("notify-all");
        return visit([event](helper::util::IPushObserver * o) {
            return o->update(event);
        });
    }
};

} //end of namespace observer
} //end of namespace test

using namespace test;
using namespace observer;
TEST(ObserverTest, testPushModeWithExactlyNotification){
    EXPECT.reset().operation("disconnect-fire").operation("notify-all").operation("DisconnectObserver-update-with-event");   
    TRACER.reset();

    DisconnectObserver obs;
    Client client;

    ASSERT_TRUE(client.attach(&obs));

    client.disconnect();

    ASSERT_TRUE(client.detach(&obs));

    ASSERT_EQ(EXPECT, TRACER);
}

namespace test {
namespace observer {
class Server : public helper::util::IPullSubject
    , public helper::util::ExactlyNotification<helper::util::IPullObserver> {
public:
    void accept(){
        TRACER.operation("Accept-event-fire");
        notify(this);
    }
    int notify(helper::util::IPullSubject * subject){
        TRACER.operation("notify-all");
        return visit([this](helper::util::IPullObserver * o){
            return o->update(this);
        });
    }
    int getState() const {
        TRACER.operation("getState");
        return 102;
    }
};
class AcceptObserver  : public helper::util::IPullObserver {
public:
    int update(helper::util::IPullSubject * subject) override {
        TRACER.operation("AcceptObserver-update"); 
        Server * server = static_cast<Server*>(subject);
        if(server != nullptr){
            server->getState();
            return 0;
        }
        return -1;
    }
};
} //end of namespace observer
} //endo of namespace test

using namespace test;
using namespace observer;
TEST(ObserverTest, testPullMode){
    EXPECT.reset().operation("Accept-event-fire").operation("notify-all").operation("AcceptObserver-update").operation("getState");
    TRACER.reset();

    AcceptObserver obs;
    Server server;
    ASSERT_TRUE(server.attach(&obs));

    server.accept();

    ASSERT_TRUE(server.detach(&obs));
    ASSERT_EQ(EXPECT, TRACER);
}

//TODO:  PushMode with AspectNotification
//TODO:  PullMode with AspectNotification
